package cn.tedu.v4;

import java.io.*;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 *  多人聊天室 Version 4
 *  多人聊天室----消息转发 ：消息广播
 *  替代  客户端也可以写多线程并发
 *  消息广播：多个客户端链接服务器 一个人说话 其他人都可以看到
 *
 * 功能：客户端之间消息转发
 * 1.服务器端创建集合  存储所有的客户端输出流
 * 2.广播消息给所有客户端   broadcastMessage   广播留言
 * 线程上面：ClientHandler
 * 3.将所有的客户端输出流添加    集合中---同步锁
 * ClientHandler---构造器  目的：多线程创建成功 立马附加消息
 * 构造器  三个参数：1.通过拨通服务器的socket  赋值当前socket
 *                   明确是否连接成功
 *                   2.拨通服务器的socket    IP地址
 *                   3.拨通服务器的socket    IP地址  同时赋予读写功能
 *                    发送客户端的数据给其他客户端【消息转发】
 *  4.收到的消息转发至所有客户端--- broadcastMessage   广播留言
 * 添加； ClientHandler---run（）进行读写
 * 5.客户端断开 需要从集合中 移除：一个客户端断开  将这个客户端移除
 *  添加； ClientHandler---run（）
  */

public class Server {
    private static final Integer PORT=1234;
    private ServerSocket serverSocket;
    //1.存储所有的客户端输出流  ：使用集合
    private List<PrintWriter> allOut=new ArrayList<>();
    //用来存储所有客户端的输出流  每当有一个新的客户端链接服务器时候
    //它的输出流【PrintWriter】会被添加至allOut  中
    //这样服务器就能向所有的客户端发送消息
    public Server() {
        try {
            serverSocket = new ServerSocket(PORT);
        } catch (BindException e) {
            System.out.println();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start()  {

        try {
            while (true) {
                System.out.println("等待客户端的连接");
               Socket socket=serverSocket.accept();
                System.out.println("一个客户端连接上了连接上了");
                new Thread(new ClientHandler(socket)).start();
            }
        } catch (IOException e) {
            System.out.println("客户端链接失败了");
        } finally {
            if (serverSocket!=null&&serverSocket.isClosed()){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    System.out.println("服务器关闭失败");
                }
                System.out.println("服务器关闭成功");
            }
        }

    }
    public static void main(String[] args) {
        new Server().start();
    }
    //2.广播消息给客户端 【同步方法】  广播留言
    //同步方法：【连接多个客户端：并发】 出现多条线程
    //     一个客户端执行完毕   在执行下客户端
    //参数：String message  客户端打印文字内容
    private synchronized void broadcastMessage(String message){
        //allOut  是集合---遍历：增强for  泛型：PrintWriter
        for (PrintWriter pw:allOut){
            pw.println("消息广播"+message);
        }
    }
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String ip;

        private PrintWriter pw;
        private BufferedReader br;

        public ClientHandler(Socket s){
            this.socket=s;
            ip=socket.getInetAddress().getHostAddress();
            try {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF8"));
                pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF8")),true);
                //3.具备功能：将客户端的输出流添加到allOut  集合中---同步锁
                //ClientHandler多线程类：处理多个客户端---多个线程
                //拨号连接---并发安全问题---加同步锁  目的：来一个客户端锁定
                //只给一个客户端加消息转发 同步锁可以监视  allOut接收一个客户端
                synchronized (allOut){
                    allOut.add(pw);
                }
            } catch (IOException e) {
                System.out.println("客户端断开连接了");
            }
        }
        public void run() {
            try {
             //   BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF8"));
              //  pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF8")),true);
            new Thread(()->{
                try {
                    String message;
                    while ((message=br.readLine())!=null){
                        System.out.println(ip+"服务器端说："+message);
                        //1.6增加一句---每次客户端发给服务器端内容
                        System.out.println("服务器端请输入：");
                        //4.服务器端收到一个链接成功客户端的信息转发给其他客户端
                        broadcastMessage(message);
                    }
                }
                catch (IOException e) {
                    System.out.println();
                }
            }).start();
                System.out.println("输入文字内容：");
                Scanner input=new Scanner(System.in);
                while (true) {
                    String say=input.nextLine();
                    pw.println("服务器端说："+say);
                    System.out.println("服务器端输入：");
                }
            } finally {
                //5.客户端断开 需要从集合中移除  加同步锁
                //为什么加同步锁：保证移除的对象就是转发消息方法的同步
                //理解：链接服务器端的客户是不是当前的
                synchronized (allOut){
                    allOut.remove(pw);
                }
                if (pw!=null)
                    pw.close();
                if (br != null)
                    try {
                        br.close();
                    }catch (IOException e){
                        System.out.println();
                    }
                //2.关闭CS多线程类中---运行的socket通讯类


                if (socket!=null&&!socket.isClosed()){
                    System.out.println(ip+"一个客户端退出服务器连接");
                    try {
                        socket.close();
                    } catch (IOException e) {
                        System.out.println(ip+"一个客户端退出链接异常");
                    }
                }

                }
            }
        }
    }

